// Package util @author dingrui @since 2021/11/28
package util

import (
	"encoding/json"
	"fmt"
	"math"
	"time"

	"github.com/bannirui/mini-prize/common"
	"github.com/bannirui/mini-prize/config"
	"github.com/bannirui/mini-prize/dal/model"
	"github.com/bannirui/mini-prize/dal/service"
	"github.com/bannirui/mini-prize/datasource"
)

const (
	userFrameSize = 2
	ipFrameSize   = 2
)

// 重置一个奖品的发奖周期信息
// 奖品剩余数量也会重新设置为当前奖品数量
// 奖品的奖品池有效数量则会设置为空
// 奖品数量、发放周期等设置有修改的时候，也需要重置
// 【难点】根据发奖周期，重新更新发奖计划
func ResetGiftPrizeData(giftInfo *model.Gift) {
	if giftInfo == nil || giftInfo.Id < 1 {
		return
	}
	id := giftInfo.Id
	nowTime := common.NowUnix()
	// 不能发奖，不需要设置发奖周期
	if giftInfo.Status == 1 || // 状态不对
		giftInfo.BeginTime >= nowTime || // 开始时间不对
		giftInfo.EndTime <= nowTime || // 结束时间不对
		giftInfo.RemainCnt <= 0 || // 剩余数不足
		giftInfo.TotalCnt <= 0 { // 总数不限制
		if giftInfo.Data != "" {
			clearGiftPrizeData(giftInfo)
		}
		return
	}
	// 不限制发奖周期，直接把奖品数量全部设置上
	dayNum := giftInfo.DurationDay
	if dayNum <= 0 {
		setGiftPool(id, giftInfo.RemainCnt)
		return
	}

	// 重新计算出来合适的奖品发放节奏
	// 奖品池的剩余数先设置为空
	setGiftPool(id, 0)

	// 每天的概率一样
	// 一天内24小时，每个小时的概率是不一样的
	// 一小时内60分钟的概率一样
	prizeNum := giftInfo.TotalCnt
	avgNum := prizeNum / dayNum

	// 每天可以分配到的奖品数量
	dayPrizeNum := make(map[int]int)
	// 平均分配，每天分到的奖品数量做分布
	if avgNum >= 1 && dayNum > 0 {
		for day := 0; day < dayNum; day++ {
			dayPrizeNum[day] = avgNum
		}
	}
	// 剩下的随机分配到任意哪天
	prizeNum -= dayNum * avgNum
	for prizeNum > 0 {
		prizeNum--
		day := common.Random(dayNum)
		_, ok := dayPrizeNum[day]
		if !ok {
			dayPrizeNum[day] = 1
		} else {
			dayPrizeNum[day] += 1
		}
	}
	// 每天的map，每小时的map，60分钟的数组，奖品数量
	prizeData := make(map[int]map[int][60]int)
	for day, num := range dayPrizeNum {
		dayPrizeData := getGiftPrizeDataOneDay(num)
		prizeData[day] = dayPrizeData
	}
	// 将周期内每天、每小时、每分钟的数据 prizeData 格式化，再序列化保存到数据表
	datalist := formatGiftPrizeData(nowTime, dayNum, prizeData)
	str, err := json.Marshal(datalist)
	if err != nil {
		// TODO: 2021/11/28
	} else {
		// 保存奖品的分布计划数据
		info := &model.Gift{
			Id:         giftInfo.Id,
			RemainCnt:  giftInfo.TotalCnt,
			HitRate:    string(str),
			BeginTime:  nowTime,
			EndTime:    nowTime + dayNum*86400,
			GmtUpdated: nowTime,
		}
		err := service.G_giftService.Update(info, nil)
		if err != nil {
			// TODO: 2021/11/28
		}
	}
}

// 将每天、每小时、每分钟的奖品数量，格式化成具体到一个时间（分钟）的奖品数量
// 结构为： [day][hour][minute]num
func formatGiftPrizeData(nowTime, dayNum int, prizeData map[int]map[int][60]int) [][2]int {
	rs := make([][2]int, 0)
	nowHour := time.Now().Hour()
	// 处理周期内每一天的计划
	for dn := 0; dn < dayNum; dn++ {
		dayData, ok := prizeData[dn]
		if !ok {
			continue
		}
		dayTime := nowTime + dn*86400
		// 处理周期内，每小时的计划
		for hn := 0; hn < 24; hn++ {
			hourData, ok := dayData[(hn+nowHour)%24]
			if !ok {
				continue
			}
			hourTime := dayTime + hn*3600
			// 处理周期内，每分钟的计划
			for mn := 0; mn < 60; mn++ {
				num := hourData[mn]
				if num <= 0 {
					continue
				}
				// 找到特定一个时间的计划数据
				minuteTime := hourTime + mn*60
				rs = append(rs, [2]int{minuteTime, num})
			}
		}
	}
	return rs
}

// 将给定的奖品数量分布到这一天的时间内
// 结构为： [hour][minute]num
func getGiftPrizeDataOneDay(num int) map[int][60]int {
	rs := make(map[int][60]int)
	hourData := [24]int{}
	// 分别将奖品分布到24个小时内
	if num > 100 {
		// 奖品数量多的时候，直接按照百分比计算出来
		for _, h := range config.PrizeDataRandomDayTime {
			hourData[h]++
		}
		for h := 0; h < 24; h++ {
			d := hourData[h]
			n := num * d / 100
			hourData[h] = n
			num -= n
		}
	}
	// 奖品数量少的时候，或者剩下了一些没有分配，需要用到随即概率来计算
	for num > 0 {
		num--
		// 通过随机数确定奖品落在哪个小时
		hourIndex := common.Random(100)
		h := config.PrizeDataRandomDayTime[hourIndex]
		hourData[h]++
	}
	// 将每个小时内的奖品数量分配到60分钟
	for h, hnum := range hourData {
		if hnum <= 0 {
			continue
		}
		minuteData := [60]int{}
		if hnum >= 60 {
			avgMinute := hnum / 60
			for i := 0; i < 60; i++ {
				minuteData[i] = avgMinute
			}
			hnum -= avgMinute * 60
		}
		// 剩下的数量不多的时候，随机到各分钟内
		for hnum > 0 {
			hnum--
			m := common.Random(60)
			minuteData[m]++
		}
		rs[h] = minuteData
	}
	return rs
}

// 清空奖品的发放计划
func clearGiftPrizeData(giftInfo *model.Gift) {
	info := &model.Gift{
		Id:   giftInfo.Id,
		Data: "",
	}
	if err := service.G_giftService.Update(info, []string{"prize_data"}); err != nil {
		// TODO: 2021/11/28
	}
	setGiftPool(giftInfo.Id, 0)
}

// 设置奖品池的数量
func setGiftPool(id, num int) {
	setServGiftPool(id, num)
}

// 设置奖品池的数量，redis缓存
func setServGiftPool(id, num int) {
	key := "gift_pool"
	_, err := datasource.G_redis.Do("HSET", key, id, num)
	if err != nil {
		// TODO: 2021/11/28
	}
}

// 今天的用户抽奖次数递增，返回递增后的数值
func IncrUserLuckyNum(uid int) int64 {
	i := uid % userFrameSize
	// 集群的redis统计数递增
	return incrServUserLucyNum(i, uid)
}

func incrServUserLucyNum(i, uid int) int64 {
	rs, err := datasource.G_redis.Do("HINCRBY", fmt.Sprintf("day_users_%d", i), uid, 1)
	if err != nil {
		// TODO: 2021/11/29
		return math.MaxInt32
	} else {
		num := rs.(int64)
		return num
	}
}

// 从给定的数据直接初始化用户的参与次数
func InitUserLuckyNum(uid int, num int64) {
	if num <= 1 {
		return
	}
	i := uid % userFrameSize
	// 集群
	initServUserLuckyNum(i, uid, num)
}

func initServUserLuckyNum(i, uid int, num int64) {
	_, err := datasource.G_redis.Do("HSET", fmt.Sprintf("day_users_%d", i), uid, num)
	if err != nil {
		// TODO: 2021/11/29
	}
	return
}

func IncrIpLuckyNum(strIp string) int64 {
	ip := common.Ip4toInt(strIp)
	i := ip % ipFrameSize
	rs, err := datasource.G_redis.Do("HINCRBY", fmt.Sprintf("day_ips_%d", i), ip, 1)
	if err != nil {
		// TODO: 2021/11/29
		return math.MaxInt32
	} else {
		return rs.(int64)
	}
}

// 获取当前奖品池中的奖品数量
func GetGiftPoolNum(id int) int {
	num := 0
	num = getServGiftPoolNum(id)
	return num
}

// 获取当前奖品池中的奖品数量，从redis中
func getServGiftPoolNum(id int) int {
	key := "gift_pool"
	rs, err := datasource.G_redis.Do("HGET", key, id)
	if err != nil {
		// TODO: 2021/11/29
		return 0
	}
	num := common.GetInt64(rs, 0)
	return int(num)
}

// 发奖，指定的奖品是否还可以发出来奖品
func PrizeGift(id, leftNum int) bool {
	ok := false
	ok = prizeServGift(id)
	if ok {
		// 更新数据库，减少奖品的库存
		rows, err := service.G_giftService.DecrLeftNum(id, 1)
		if rows < 1 || err != nil {
			// TODO: 2021/11/29
			// 数据更新失败，不能发奖
			return false
		}
	}
	return ok
}

// 发奖，redis缓存
func prizeServGift(id int) bool {
	rs, err := datasource.G_redis.Do("HINCRBY", "gift_pool", id, -1)
	if err != nil {
		// TODO: 2021/11/29
		return false
	}
	num := common.GetInt64(rs, -1)
	if num >= 0 {
		return true
	} else {
		return false
	}
}

// 优惠券类的发放
func PrizeCodeDiff(id int) string {
	return prizeServCodeDiff(id)
}

// 优惠券发放，使用redis的方式发放
func prizeServCodeDiff(id int) string {
	rs, err := datasource.G_redis.Do("SPOP", fmt.Sprintf("gift_code_%d", id))
	if err != nil {
		// TODO: 2021/11/29
		return ""
	}
	code := common.GetString(rs, "")
	if code == "" {
		// TODO: 2021/11/29
		return ""
	}
	// 更新数据库中的发放状态
	_ = service.G_couponService.UpdateByCode(&model.Coupon{
		Code:       code,
		Status:     2,
		GmtUpdated: common.NowUnix(),
	}, nil)
	return code
}
